package org.g4studio.core.mvc.xstruts.util;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.g4studio.core.mvc.xstruts.Globals;
import org.g4studio.core.mvc.xstruts.action.ActionForm;
import org.g4studio.core.mvc.xstruts.action.ActionMapping;
import org.g4studio.core.mvc.xstruts.action.ActionServlet;
import org.g4studio.core.mvc.xstruts.action.ActionServletWrapper;
import org.g4studio.core.mvc.xstruts.config.ActionConfig;
import org.g4studio.core.mvc.xstruts.config.FormBeanConfig;
import org.g4studio.core.mvc.xstruts.config.ForwardConfig;
import org.g4studio.core.mvc.xstruts.config.ModuleConfig;
import org.g4studio.core.mvc.xstruts.upload.MultipartRequestHandler;
import org.g4studio.core.mvc.xstruts.upload.MultipartRequestWrapper;

/**
 * <p>
 * General purpose utility methods related to processing a servlet request in
 * the Struts controller framework.
 * </p>
 * 
 * @version $Rev: 421119 $ $Date: 2006-07-11 21:49:11 -0700 (Tue, 11 Jul 2006) $
 */
public class RequestUtils {
	// ------------------------------------------------------- Static Variables

	/**
	 * <p>
	 * Commons Logging instance.
	 * </p>
	 */
	protected static Log log = LogFactory.getLog(RequestUtils.class);

	// --------------------------------------------------------- Public Methods

	/**
	 * <p>
	 * Create and return an absolute URL for the specified context-relative
	 * path, based on the server and context information in the specified
	 * request.
	 * </p>
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @param path
	 *            The context-relative path (must start with '/')
	 * @return absolute URL based on context-relative path
	 * @throws MalformedURLException
	 *             if we cannot create an absolute URL
	 */
	public static URL absoluteURL(HttpServletRequest request, String path) throws MalformedURLException {
		return (new URL(serverURL(request), request.getContextPath() + path));
	}

	/**
	 * <p>
	 * Return the <code>Class</code> object for the specified fully qualified
	 * class name, from this web application's class loader.
	 * </p>
	 * 
	 * @param className
	 *            Fully qualified class name to be loaded
	 * @return Class object
	 * @throws ClassNotFoundException
	 *             if the class cannot be found
	 */
	public static Class applicationClass(String className) throws ClassNotFoundException {
		return applicationClass(className, null);
	}

	/**
	 * <p>
	 * Return the <code>Class</code> object for the specified fully qualified
	 * class name, from this web application's class loader.
	 * </p>
	 * 
	 * @param className
	 *            Fully qualified class name to be loaded
	 * @param classLoader
	 *            The desired classloader to use
	 * @return Class object
	 * @throws ClassNotFoundException
	 *             if the class cannot be found
	 */
	public static Class applicationClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
		if (classLoader == null) {
			// Look up the class loader to be used
			classLoader = Thread.currentThread().getContextClassLoader();

			if (classLoader == null) {
				classLoader = RequestUtils.class.getClassLoader();
			}
		}

		// Attempt to load the specified class
		return (classLoader.loadClass(className));
	}

	/**
	 * <p>
	 * Return a new instance of the specified fully qualified class name, after
	 * loading the class from this web application's class loader. The specified
	 * class <strong>MUST</strong> have a public zero-arguments constructor.
	 * </p>
	 * 
	 * @param className
	 *            Fully qualified class name to use
	 * @return new instance of class
	 * @throws ClassNotFoundException
	 *             if the class cannot be found
	 * @throws IllegalAccessException
	 *             if the class or its constructor is not accessible
	 * @throws InstantiationException
	 *             if this class represents an abstract class, an interface, an
	 *             array class, a primitive type, or void
	 * @throws InstantiationException
	 *             if this class has no zero-arguments constructor
	 */
	public static Object applicationInstance(String className) throws ClassNotFoundException, IllegalAccessException,
			InstantiationException {
		return applicationInstance(className, null);
	}

	/**
	 * <p>
	 * Return a new instance of the specified fully qualified class name, after
	 * loading the class from this web application's class loader. The specified
	 * class <strong>MUST</strong> have a public zero-arguments constructor.
	 * </p>
	 * 
	 * @param className
	 *            Fully qualified class name to use
	 * @param classLoader
	 *            The desired classloader to use
	 * @return new instance of class
	 * @throws ClassNotFoundException
	 *             if the class cannot be found
	 * @throws IllegalAccessException
	 *             if the class or its constructor is not accessible
	 * @throws InstantiationException
	 *             if this class represents an abstract class, an interface, an
	 *             array class, a primitive type, or void
	 * @throws InstantiationException
	 *             if this class has no zero-arguments constructor
	 */
	public static Object applicationInstance(String className, ClassLoader classLoader) throws ClassNotFoundException,
			IllegalAccessException, InstantiationException {
		return (applicationClass(className, classLoader).newInstance());
	}

	/**
	 * <p>
	 * Create (if necessary) and return an <code>ActionForm</code> instance
	 * appropriate for this request. If no <code>ActionForm</code> instance is
	 * required, return <code>null</code>.
	 * </p>
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @param mapping
	 *            The action mapping for this request
	 * @param moduleConfig
	 *            The configuration for this module
	 * @param servlet
	 *            The action servlet
	 * @return ActionForm instance associated with this request
	 */
	public static ActionForm createActionForm(HttpServletRequest request, ActionMapping mapping,
			ModuleConfig moduleConfig, ActionServlet servlet) {
		// Is there a form bean associated with this mapping?
		String attribute = mapping.getAttribute();

		if (attribute == null) {
			return (null);
		}

		// Look up the form bean configuration information to use
		String name = mapping.getName();
		FormBeanConfig config = moduleConfig.findFormBeanConfig(name);

		if (config == null) {
			log.warn("No FormBeanConfig found under '" + name + "'");

			return (null);
		}

		ActionForm instance = lookupActionForm(request, attribute, mapping.getScope());

		// Can we recycle the existing form bean instance (if there is one)?
		if ((instance != null) && config.canReuse(instance)) {
			return (instance);
		}

		return createActionForm(config, servlet);
	}

	private static ActionForm lookupActionForm(HttpServletRequest request, String attribute, String scope) {
		// Look up any existing form bean instance
		if (log.isDebugEnabled()) {
			log.debug(" Looking for ActionForm bean instance in scope '" + scope + "' under attribute key '"
					+ attribute + "'");
		}

		ActionForm instance = null;
		HttpSession session = null;

		if ("request".equals(scope)) {
			instance = (ActionForm) request.getAttribute(attribute);
		} else {
			session = request.getSession();
			instance = (ActionForm) session.getAttribute(attribute);
		}

		return (instance);
	}

	/**
	 * <p>
	 * Create and return an <code>ActionForm</code> instance appropriate to the
	 * information in <code>config</code>.
	 * </p>
	 * 
	 * <p>
	 * Does not perform any checks to see if an existing ActionForm exists which
	 * could be reused.
	 * </p>
	 * 
	 * @param config
	 *            The configuration for the Form bean which is to be created.
	 * @param servlet
	 *            The action servlet
	 * @return ActionForm instance associated with this request
	 */
	public static ActionForm createActionForm(FormBeanConfig config, ActionServlet servlet) {
		if (config == null) {
			return (null);
		}

		ActionForm instance = null;

		// Create and return a new form bean instance
		try {
			instance = config.createActionForm(servlet);

			if (log.isDebugEnabled()) {
				log.debug(" Creating new " + (config.getDynamic() ? "DynaActionForm" : "ActionForm")
						+ " instance of type '" + config.getType() + "'");
				log.trace(" --> " + instance);
			}
		} catch (Throwable t) {
			log.error(servlet.getInternal().getMessage("formBean", config.getType()), t);
		}

		return (instance);
	}

	/**
	 * <p>
	 * Look up and return current user locale, based on the specified
	 * parameters.
	 * </p>
	 * 
	 * @param request
	 *            The request used to lookup the Locale
	 * @param locale
	 *            Name of the session attribute for our user's Locale. If this
	 *            is <code>null</code>, the default locale key is used for the
	 *            lookup.
	 * @return current user locale
	 * @since Struts 1.2
	 */
	public static Locale getUserLocale(HttpServletRequest request, String locale) {
		Locale userLocale = null;
		HttpSession session = request.getSession(false);

		if (locale == null) {
			locale = Globals.LOCALE_KEY;
		}

		// Only check session if sessions are enabled
		if (session != null) {
			userLocale = (Locale) session.getAttribute(locale);
		}

		if (userLocale == null) {
			// Returns Locale based on Accept-Language header or the server
			// default
			userLocale = request.getLocale();
		}

		return userLocale;
	}

	/**
	 * <p>
	 * Populate the properties of the specified JavaBean from the specified HTTP
	 * request, based on matching each parameter name against the corresponding
	 * JavaBeans "property setter" methods in the bean's class. Suitable
	 * conversion is done for argument types as described under
	 * <code>convert()</code>.
	 * </p>
	 * 
	 * @param bean
	 *            The JavaBean whose properties are to be set
	 * @param request
	 *            The HTTP request whose parameters are to be used to populate
	 *            bean properties
	 * @throws ServletException
	 *             if an exception is thrown while setting property values
	 */
	public static void populate(Object bean, HttpServletRequest request) throws ServletException {
		populate(bean, null, null, request);
	}

	/**
	 * <p>
	 * Populate the properties of the specified JavaBean from the specified HTTP
	 * request, based on matching each parameter name (plus an optional prefix
	 * and/or suffix) against the corresponding JavaBeans "property setter"
	 * methods in the bean's class. Suitable conversion is done for argument
	 * types as described under <code>setProperties</code>.
	 * </p>
	 * 
	 * <p>
	 * If you specify a non-null <code>prefix</code> and a non-null
	 * <code>suffix</code>, the parameter name must match <strong>both</strong>
	 * conditions for its value(s) to be used in populating bean properties. If
	 * the request's content type is "multipart/form-data" and the method is
	 * "POST", the <code>HttpServletRequest</code> object will be wrapped in a
	 * <code>MultipartRequestWrapper</code object.
	 * </p>
	 * 
	 * @param bean
	 *            The JavaBean whose properties are to be set
	 * @param prefix
	 *            The prefix (if any) to be prepend to bean property names when
	 *            looking for matching parameters
	 * @param suffix
	 *            The suffix (if any) to be appended to bean property names when
	 *            looking for matching parameters
	 * @param request
	 *            The HTTP request whose parameters are to be used to populate
	 *            bean properties
	 * @throws ServletException
	 *             if an exception is thrown while setting property values
	 */
	public static void populate(Object bean, String prefix, String suffix, HttpServletRequest request)
			throws ServletException {
		// Build a list of relevant request parameters from this request
		HashMap properties = new HashMap();

		// Iterator of parameter names
		Enumeration names = null;

		// Map for multipart parameters
		Map multipartParameters = null;

		String contentType = request.getContentType();
		String method = request.getMethod();
		boolean isMultipart = false;

		if (bean instanceof ActionForm) {
			((ActionForm) bean).setMultipartRequestHandler(null);
		}

		MultipartRequestHandler multipartHandler = null;
		if ((contentType != null) && (contentType.startsWith("multipart/form-data"))
				&& (method.equalsIgnoreCase("POST"))) {
			// Get the ActionServletWrapper from the form bean
			ActionServletWrapper servlet;

			if (bean instanceof ActionForm) {
				servlet = ((ActionForm) bean).getServletWrapper();
			} else {
				throw new ServletException("bean that's supposed to be "
						+ "populated from a multipart request is not of type "
						+ "\"org.g4studio.core.mvc.xstruts.action.ActionForm\", but type " + "\""
						+ bean.getClass().getName() + "\"");
			}

			// Obtain a MultipartRequestHandler
			multipartHandler = getMultipartHandler(request);

			if (multipartHandler != null) {
				isMultipart = true;

				// Set servlet and mapping info
				servlet.setServletFor(multipartHandler);
				multipartHandler.setMapping((ActionMapping) request.getAttribute(Globals.MAPPING_KEY));

				// Initialize multipart request class handler
				multipartHandler.handleRequest(request);

				// stop here if the maximum length has been exceeded
				Boolean maxLengthExceeded = (Boolean) request
						.getAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED);

				if ((maxLengthExceeded != null) && (maxLengthExceeded.booleanValue())) {
					((ActionForm) bean).setMultipartRequestHandler(multipartHandler);
					return;
				}

				// retrieve form values and put into properties
				multipartParameters = getAllParametersForMultipartRequest(request, multipartHandler);
				names = Collections.enumeration(multipartParameters.keySet());
			}
		}

		if (!isMultipart) {
			names = request.getParameterNames();
		}

		while (names.hasMoreElements()) {
			String name = (String) names.nextElement();
			String stripped = name;

			if (prefix != null) {
				if (!stripped.startsWith(prefix)) {
					continue;
				}

				stripped = stripped.substring(prefix.length());
			}

			if (suffix != null) {
				if (!stripped.endsWith(suffix)) {
					continue;
				}

				stripped = stripped.substring(0, stripped.length() - suffix.length());
			}

			Object parameterValue = null;

			if (isMultipart) {
				parameterValue = multipartParameters.get(name);
			} else {
				parameterValue = request.getParameterValues(name);
			}

			// Populate parameters, except "standard" struts attributes
			// such as 'org.g4studio.core.mvc.xstruts.action.CANCEL'
			if (!(stripped.startsWith("org.g4studio.core.mvc.xstruts."))) {
				properties.put(stripped, parameterValue);
			}
		}

		// Set the corresponding properties of our bean
		try {
			BeanUtils.populate(bean, properties);
		} catch (Exception e) {
			throw new ServletException("BeanUtils.populate", e);
		} finally {
			if (multipartHandler != null) {
				// Set the multipart request handler for our ActionForm.
				// If the bean isn't an ActionForm, an exception would have been
				// thrown earlier, so it's safe to assume that our bean is
				// in fact an ActionForm.
				((ActionForm) bean).setMultipartRequestHandler(multipartHandler);
			}
		}
	}

	/**
	 * <p>
	 * Try to locate a multipart request handler for this request. First, look
	 * for a mapping-specific handler stored for us under an attribute. If one
	 * is not present, use the global multipart handler, if there is one.
	 * </p>
	 * 
	 * @param request
	 *            The HTTP request for which the multipart handler should be
	 *            found.
	 * @return the multipart handler to use, or null if none is found.
	 * @throws ServletException
	 *             if any exception is thrown while attempting to locate the
	 *             multipart handler.
	 */
	private static MultipartRequestHandler getMultipartHandler(HttpServletRequest request) throws ServletException {
		MultipartRequestHandler multipartHandler = null;
		String multipartClass = (String) request.getAttribute(Globals.MULTIPART_KEY);

		request.removeAttribute(Globals.MULTIPART_KEY);

		// Try to initialize the mapping specific request handler
		if (multipartClass != null) {
			try {
				multipartHandler = (MultipartRequestHandler) applicationInstance(multipartClass);
			} catch (ClassNotFoundException cnfe) {
				log.error("MultipartRequestHandler class \"" + multipartClass + "\" in mapping class not found, "
						+ "defaulting to global multipart class");
			} catch (InstantiationException ie) {
				log.error("InstantiationException when instantiating " + "MultipartRequestHandler \"" + multipartClass
						+ "\", " + "defaulting to global multipart class, exception: " + ie.getMessage());
			} catch (IllegalAccessException iae) {
				log.error("IllegalAccessException when instantiating " + "MultipartRequestHandler \"" + multipartClass
						+ "\", " + "defaulting to global multipart class, exception: " + iae.getMessage());
			}

			if (multipartHandler != null) {
				return multipartHandler;
			}
		}

		ModuleConfig moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);

		multipartClass = moduleConfig.getControllerConfig().getMultipartClass();

		// Try to initialize the global request handler
		if (multipartClass != null) {
			try {
				multipartHandler = (MultipartRequestHandler) applicationInstance(multipartClass);
			} catch (ClassNotFoundException cnfe) {
				throw new ServletException("Cannot find multipart class \"" + multipartClass + "\"" + ", exception: "
						+ cnfe.getMessage());
			} catch (InstantiationException ie) {
				throw new ServletException("InstantiationException when instantiating " + "multipart class \""
						+ multipartClass + "\", exception: " + ie.getMessage());
			} catch (IllegalAccessException iae) {
				throw new ServletException("IllegalAccessException when instantiating " + "multipart class \""
						+ multipartClass + "\", exception: " + iae.getMessage());
			}

			if (multipartHandler != null) {
				return multipartHandler;
			}
		}

		return multipartHandler;
	}

	/**
	 * <p>
	 * Create a <code>Map</code> containing all of the parameters supplied for a
	 * multipart request, keyed by parameter name. In addition to text and file
	 * elements from the multipart body, query string parameters are included as
	 * well.
	 * </p>
	 * 
	 * @param request
	 *            The (wrapped) HTTP request whose parameters are to be added to
	 *            the map.
	 * @param multipartHandler
	 *            The multipart handler used to parse the request.
	 * @return the map containing all parameters for this multipart request.
	 */
	private static Map getAllParametersForMultipartRequest(HttpServletRequest request,
			MultipartRequestHandler multipartHandler) {
		Map parameters = new HashMap();
		Hashtable elements = multipartHandler.getAllElements();
		Enumeration e = elements.keys();

		while (e.hasMoreElements()) {
			String key = (String) e.nextElement();

			parameters.put(key, elements.get(key));
		}

		if (request instanceof MultipartRequestWrapper) {
			request = (HttpServletRequest) ((MultipartRequestWrapper) request).getRequest();
			e = request.getParameterNames();

			while (e.hasMoreElements()) {
				String key = (String) e.nextElement();

				parameters.put(key, request.getParameterValues(key));
			}
		} else {
			log.debug("Gathering multipart parameters for unwrapped request");
		}

		return parameters;
	}

	/**
	 * <p>
	 * Compute the printable representation of a URL, leaving off the
	 * scheme/host/port part if no host is specified. This will typically be the
	 * case for URLs that were originally created from relative or
	 * context-relative URIs.
	 * </p>
	 * 
	 * @param url
	 *            URL to render in a printable representation
	 * @return printable representation of a URL
	 */
	public static String printableURL(URL url) {
		if (url.getHost() != null) {
			return (url.toString());
		}

		String file = url.getFile();
		String ref = url.getRef();

		if (ref == null) {
			return (file);
		} else {
			StringBuffer sb = new StringBuffer(file);

			sb.append('#');
			sb.append(ref);

			return (sb.toString());
		}
	}

	/**
	 * <p>
	 * Return the context-relative URL that corresponds to the specified
	 * {@link ActionConfig}, relative to the module associated with the current
	 * modules's {@link ModuleConfig}.
	 * </p>
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @param action
	 *            ActionConfig to be evaluated
	 * @param pattern
	 *            URL pattern used to map the controller servlet
	 * @return context-relative URL relative to the module
	 * @since Struts 1.1
	 */
	public static String actionURL(HttpServletRequest request, ActionConfig action, String pattern) {
		StringBuffer sb = new StringBuffer();

		if (pattern.endsWith("/*")) {
			sb.append(pattern.substring(0, pattern.length() - 2));
			sb.append(action.getPath());
		} else if (pattern.startsWith("*.")) {
			ModuleConfig appConfig = ModuleUtils.getInstance().getModuleConfig(request);

			sb.append(appConfig.getPrefix());
			sb.append(action.getPath());
			sb.append(pattern.substring(1));
		} else {
			throw new IllegalArgumentException(pattern);
		}

		return sb.toString();
	}

	/**
	 * <p>
	 * Return the context-relative URL that corresponds to the specified
	 * <code>ForwardConfig</code>. The URL is calculated based on the properties
	 * of the {@link ForwardConfig} instance as follows:
	 * </p>
	 * 
	 * <ul>
	 * 
	 * 
	 * <li>If the <code>contextRelative</code> property is set, it is assumed
	 * that the <code>path</code> property contains a path that is already
	 * context-relative:
	 * 
	 * <ul>
	 * 
	 * <li>If the <code>path</code> property value starts with a slash, it is
	 * returned unmodified.</li>
	 * <li>If the <code>path</code> property value does not start with a slash,
	 * a slash is prepended.</li>
	 * 
	 * </ul>
	 * </li>
	 * 
	 * <li>Acquire the <code>forwardPattern</code> property from the
	 * <code>ControllerConfig</code> for the application module used to process
	 * this request. If no pattern was configured, default to a pattern of
	 * <code>$M$P</code>, which is compatible with the hard-coded mapping
	 * behavior in Struts 1.0.</li>
	 * 
	 * <li>Process the acquired <code>forwardPattern</code>, performing the
	 * following substitutions:
	 * 
	 * <ul>
	 * 
	 * <li><strong>$M</strong> - Replaced by the module prefix for the
	 * application module processing this request.</li>
	 * 
	 * <li><strong>$P</strong> - Replaced by the <code>path</code> property of
	 * the specified {@link ForwardConfig}, prepended with a slash if it does
	 * not start with one.</li>
	 * 
	 * <li><strong>$$</strong> - Replaced by a single dollar sign character.</li>
	 * 
	 * <li><strong>$x</strong> (where "x" is any charater not listed above) -
	 * Silently omit these two characters from the result value. (This has the
	 * side effect of causing all other $+letter combinations to be reserved.)</li>
	 * 
	 * </ul>
	 * </li>
	 * 
	 * </ul>
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @param forward
	 *            ForwardConfig to be evaluated
	 * @return context-relative URL
	 * @since Struts 1.1
	 */
	public static String forwardURL(HttpServletRequest request, ForwardConfig forward) {
		return forwardURL(request, forward, null);
	}

	/**
	 * <p>
	 * Return the context-relative URL that corresponds to the specified
	 * <code>ForwardConfig</code>. The URL is calculated based on the properties
	 * of the {@link ForwardConfig} instance as follows:
	 * </p>
	 * 
	 * <ul>
	 * 
	 * <li>If the <code>contextRelative</code> property is set, it is assumed
	 * that the <code>path</code> property contains a path that is already
	 * context-relative:
	 * <ul>
	 * 
	 * <li>If the <code>path</code> property value starts with a slash, it is
	 * returned unmodified.</li>
	 * <li>If the <code>path</code> property value does not start with a slash,
	 * a slash is prepended.</li>
	 * 
	 * </ul>
	 * </li>
	 * 
	 * <li>Acquire the <code>forwardPattern</code> property from the
	 * <code>ControllerConfig</code> for the application module used to process
	 * this request. If no pattern was configured, default to a pattern of
	 * <code>$M$P</code>, which is compatible with the hard-coded mapping
	 * behavior in Struts 1.0.</li>
	 * 
	 * <li>Process the acquired <code>forwardPattern</code>, performing the
	 * following substitutions:
	 * <ul>
	 * <li><strong>$M</strong> - Replaced by the module prefix for the
	 * application module processing this request.</li>
	 * 
	 * <li><strong>$P</strong> - Replaced by the <code>path</code> property of
	 * the specified {@link ForwardConfig}, prepended with a slash if it does
	 * not start with one.</li>
	 * 
	 * <li><strong>$$</strong> - Replaced by a single dollar sign character.</li>
	 * 
	 * <li><strong>$x</strong> (where "x" is any charater not listed above) -
	 * Silently omit these two characters from the result value. (This has the
	 * side effect of causing all other $+letter combinations to be reserved.)</li>
	 * 
	 * </ul>
	 * </li>
	 * </ul>
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @param forward
	 *            ForwardConfig to be evaluated
	 * @param moduleConfig
	 *            Base forward on this module config.
	 * @return context-relative URL
	 * @since Struts 1.2
	 */
	public static String forwardURL(HttpServletRequest request, ForwardConfig forward, ModuleConfig moduleConfig) {
		// load the current moduleConfig, if null
		if (moduleConfig == null) {
			moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);
		}

		String path = forward.getPath();

		// load default prefix
		String prefix = moduleConfig.getPrefix();

		// override prefix if supplied by forward
		if (forward.getModule() != null) {
			prefix = forward.getModule();

			if ("/".equals(prefix)) {
				prefix = "";
			}
		}

		StringBuffer sb = new StringBuffer();

		// Calculate a context relative path for this ForwardConfig
		String forwardPattern = moduleConfig.getControllerConfig().getForwardPattern();

		if (forwardPattern == null) {
			// Performance optimization for previous default behavior
			sb.append(prefix);

			// smoothly insert a '/' if needed
			if (!path.startsWith("/")) {
				sb.append("/");
			}

			sb.append(path);
		} else {
			boolean dollar = false;

			for (int i = 0; i < forwardPattern.length(); i++) {
				char ch = forwardPattern.charAt(i);

				if (dollar) {
					switch (ch) {
					case 'M':
						sb.append(prefix);

						break;

					case 'P':

						// add '/' if needed
						if (!path.startsWith("/")) {
							sb.append("/");
						}

						sb.append(path);

						break;

					case '$':
						sb.append('$');

						break;

					default:
						; // Silently swallow
					}

					dollar = false;

					continue;
				} else if (ch == '$') {
					dollar = true;
				} else {
					sb.append(ch);
				}
			}
		}

		return (sb.toString());
	}

	/**
	 * <p>
	 * Return the URL representing the current request. This is equivalent to
	 * <code>HttpServletRequest.getRequestURL</code> in Servlet 2.3.
	 * </p>
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @return URL representing the current request
	 * @throws MalformedURLException
	 *             if a URL cannot be created
	 */
	public static URL requestURL(HttpServletRequest request) throws MalformedURLException {
		StringBuffer url = requestToServerUriStringBuffer(request);

		return (new URL(url.toString()));
	}

	/**
	 * <p>
	 * Return the URL representing the scheme, server, and port number of the
	 * current request. Server-relative URLs can be created by simply appending
	 * the server-relative path (starting with '/') to this.
	 * </p>
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @return URL representing the scheme, server, and port number of the
	 *         current request
	 * @throws MalformedURLException
	 *             if a URL cannot be created
	 */
	public static URL serverURL(HttpServletRequest request) throws MalformedURLException {
		StringBuffer url = requestToServerStringBuffer(request);

		return (new URL(url.toString()));
	}

	/**
	 * <p>
	 * Return the string representing the scheme, server, and port number of the
	 * current request. Server-relative URLs can be created by simply appending
	 * the server-relative path (starting with '/') to this.
	 * </p>
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @return URL representing the scheme, server, and port number of the
	 *         current request
	 * @since Struts 1.2.0
	 */
	public static StringBuffer requestToServerUriStringBuffer(HttpServletRequest request) {
		StringBuffer serverUri = createServerUriStringBuffer(request.getScheme(), request.getServerName(),
				request.getServerPort(), request.getRequestURI());

		return serverUri;
	}

	/**
	 * <p>
	 * Return <code>StringBuffer</code> representing the scheme, server, and
	 * port number of the current request. Server-relative URLs can be created
	 * by simply appending the server-relative path (starting with '/') to this.
	 * </p>
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @return URL representing the scheme, server, and port number of the
	 *         current request
	 * @since Struts 1.2.0
	 */
	public static StringBuffer requestToServerStringBuffer(HttpServletRequest request) {
		return createServerStringBuffer(request.getScheme(), request.getServerName(), request.getServerPort());
	}

	/**
	 * <p>
	 * Return <code>StringBuffer</code> representing the scheme, server, and
	 * port number of the current request.
	 * </p>
	 * 
	 * @param scheme
	 *            The scheme name to use
	 * @param server
	 *            The server name to use
	 * @param port
	 *            The port value to use
	 * @return StringBuffer in the form scheme: server: port
	 * @since Struts 1.2.0
	 */
	public static StringBuffer createServerStringBuffer(String scheme, String server, int port) {
		StringBuffer url = new StringBuffer();

		if (port < 0) {
			port = 80; // Work around java.net.URL bug
		}

		url.append(scheme);
		url.append("://");
		url.append(server);

		if ((scheme.equals("http") && (port != 80)) || (scheme.equals("https") && (port != 443))) {
			url.append(':');
			url.append(port);
		}

		return url;
	}

	/**
	 * <p>
	 * Return <code>StringBuffer</code> representing the scheme, server, and
	 * port number of the current request.
	 * </p>
	 * 
	 * @param scheme
	 *            The scheme name to use
	 * @param server
	 *            The server name to use
	 * @param port
	 *            The port value to use
	 * @param uri
	 *            The uri value to use
	 * @return StringBuffer in the form scheme: server: port
	 * @since Struts 1.2.0
	 */
	public static StringBuffer createServerUriStringBuffer(String scheme, String server, int port, String uri) {
		StringBuffer serverUri = createServerStringBuffer(scheme, server, port);

		serverUri.append(uri);

		return serverUri;
	}
}
